﻿using System;
using System.Data;
using System.Linq;
using Jet.Dal.EF.Implementation;
using Jet.Dal.EF.Implementation.Contexts;
using Jet.Dal.EF.Tests.DataObjects;
using NUnit.Framework;

namespace Jet.Dal.EF.Tests
{
	[TestFixture]
	public class EFRepositoryTests
	{
		private Customer CreateCustomer(IEntityFactoryRepository<EntityKey, Customer> repo)
		{
			var c = repo.Create();
			c.CustomerID = Guid.NewGuid().ToString();
			c.CompanyName = "Gazprom";
			c.ContactName = "Medved";
			c.Address = "Piter";
			return c;
		}

		[Test]
		public void TestCtor()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, false, false);
			var repo = new EFRepository<Customer>(context);
			Assert.IsNotNull(repo);
			Assert.That(() => new EFRepository<Customer>(null),
			  Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void TestQuerying()
		{
			var repo = new EFRepository<Customer>(new DbContext1<Customer>(Constants.ConnectionString, true, true));
			var query = from c in repo.GetQuery()
						where c.Country == "Germany"
						select c;
			Assert.IsTrue(query.Any());
			Assert.That(() => new EFRepository<Region>(new DbContext1<Order>(Constants.ConnectionString, true, true)),
			  Throws.TypeOf<InvalidOperationException>());
		}

		[Test]
		public void TestQueryingNavigationsExceptions()
		{
			using (var context = new DbContext2<Customer, Order>(Constants.ConnectionString, true, true))
			{
				var repo = new EFRepository<Customer>(context);
				Assert.That(() => repo.GetQuery(null), Throws.TypeOf<ArgumentNullException>());
				Assert.That(() => repo.GetQuery(new[] { "Test", null }), 
					Throws.TypeOf<ArgumentNullException>());
				Assert.That(() => repo.GetQuery(new[] { "Test", String.Empty }),
					Throws.TypeOf<ArgumentNullException>());
			}
		}

		[Test]
		public void TestQueryingNavigationsWithoutLazyLoading()
		{
			using (var context = new DbContext3<Customer, Order, Employee>(Constants.ConnectionString, false, false))
			{
				var repo = new EFRepository<Order>(context);
				var query = from o in repo.GetQuery(new[] { "Customer", "Employee" })
							where (o.CustomerID != "") && (o.CustomerID != null) && (o.EmployeeID > 0)
							select o;
				var result = query.ToArray();
				if (result.Length == 0)
				{
					Assert.Inconclusive("Query should return some data");
				}
				foreach (var order in result)
				{
					Assert.IsNotNull(order.Customer);
					Assert.IsNotNull(order.Employee);
				}
			}
		}

		[Test]
		public void TestQueryingInvalidNavigations()
		{
			using (var context = new DbContext3<Customer, Order, Employee>(Constants.ConnectionString, false, false))
			{
				var repo = new EFRepository<Order>(context);
				var query = from o in repo.GetQuery(new[] { "Customersdf", "Employee" })
							where (o.CustomerID != "") && (o.CustomerID != null) && (o.EmployeeID > 0)
							select o;
				Assert.That(() => query.ToArray(), Throws.TypeOf<InvalidOperationException>());
			}
		}

		[Test]
		public void TestQueryingNavigationsWithLazyLoading()
		{
			//Testing navigation loading and lazy loading
			using (var context = new DbContext3<Customer, Order, Employee>(Constants.ConnectionString, true, true))
			{
				var repo = new EFRepository<Order>(context);
				var query = from o in repo.GetQuery(new[] { "Customer" })
							where (o.CustomerID != "") && (o.CustomerID != null) && (o.EmployeeID > 0)
							select o;
				var result = query.ToArray();
				if (result.Length == 0)
				{
					Assert.Inconclusive("Query should return some data");
				}
				foreach (var order in result)
				{
					Assert.IsNotNull(order.Customer);
					Assert.IsNotNull(order.Employee);
				}
			}
		}

		[Test]
		public void TestQueryingLongNavigations()
		{
			using (var context = new DbContext3<Customer, Order, Employee>(Constants.ConnectionString, false, false))
			{
				var repo = new EFRepository<Customer>(context);
				var query = from c in repo.GetQuery(new[] { "Orders", "Orders.Employee" })
							select c;
				var result = query.ToArray();
				if (result.Length == 0)
				{
					Assert.Inconclusive("Query should return some customers");
				}
				var employesCount = 0;
				foreach (var customer in result)
				{
					Assert.IsNotNull(customer.Orders);
					foreach (var order in customer.Orders)
					{
						Assert.IsNotNull(order.Employee);
						employesCount++;
					}
				}
				if (employesCount == 0)
				{
					Assert.Inconclusive("Orders should have reference to employees");
				}
			}
		}

		[Test]
		public void TestAttach()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customer = CreateCustomer(repo);
			repo.Attach(customer);
			var set = context.Set<Customer>();
			Assert.AreEqual(set.Local.Count, 1);
			Assert.AreEqual(context.Entry(customer).State, System.Data.EntityState.Unchanged);
			Assert.AreSame(set.Local[0], customer);
			Assert.That(() => repo.Attach(null), Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void TestCreate()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customer = repo.Create();
			Assert.That(customer, !Is.Null);
			const string id = "SomeTestKey";
			var key = new EntityKey("DbContext11.DbSet1", "CustomerID", id);
			var c2 = repo.Create(key);
			Assert.That(c2, !Is.Null);
			Assert.That(c2.CustomerID, Is.EqualTo(id));
		}

		[Test]
		public void TestInsert()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customer = CreateCustomer(repo);
			repo.Insert(customer);
			var set = context.Set<Customer>();
			Assert.AreEqual(set.Local.Count, 1);
			Assert.AreEqual(context.Entry(customer).State, System.Data.EntityState.Added);
			Assert.AreSame(set.Local[0], customer);
			Assert.That(() => repo.Insert(null), Throws.TypeOf<ArgumentNullException>());
		}


		[Test]
		public void TestUpdate()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customer = CreateCustomer(repo);
			repo.Update(customer);
			var set = context.Set<Customer>();
			Assert.AreEqual(set.Local.Count, 1);
			Assert.AreEqual(context.Entry(customer).State, System.Data.EntityState.Modified);
			Assert.AreSame(set.Local[0], customer);
			Assert.That(() => repo.Update(null), Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void TestDelete()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customers = repo.GetQuery().ToArray();
			var set = context.Set<Customer>();
			Assert.IsTrue(set.Local.Count > 1);
			var customer = customers.First();
			repo.Delete(customer);
			Assert.AreEqual(context.Entry(customer).State, System.Data.EntityState.Deleted);
			Assert.That(() => repo.Delete(null), Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void TestDetach()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customers = repo.GetQuery().ToArray();
			var set = context.Set<Customer>();
			Assert.IsTrue(set.Local.Count > 1);
			var customer = customers.First();
			repo.Detach(customer);
			Assert.AreEqual(context.Entry(customer).State, System.Data.EntityState.Detached);
			Assert.That(() => repo.Detach(null), Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void TestGetState()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customers = repo.GetQuery().ToArray();
			var set = context.Set<Customer>();
			Assert.IsTrue(set.Local.Count > 5);
			var customer = customers[0];
			repo.Detach(customer);
			Assert.AreEqual(repo.GetState(customer), EntityState.Detached);
			customer = customers[1];
			repo.Delete(customer);
			Assert.AreEqual(repo.GetState(customer), EntityState.Deleted);
			customer = customers[2];
			Assert.AreEqual(repo.GetState(customer), EntityState.Unchanged);
			customer.ContactName = "Miller";
			Assert.AreEqual(repo.GetState(customer), EntityState.Modified);
			customer = CreateCustomer(repo);
			repo.Attach(customer);
			Assert.AreEqual(repo.GetState(customer), EntityState.Unchanged);
			customer = CreateCustomer(repo);
			repo.Insert(customer);
			Assert.AreEqual(repo.GetState(customer), EntityState.Added);
			Assert.That(() => repo.GetState(null), Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void TestCreateKey()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customers = repo.GetQuery().ToArray();
			var key = repo.CreateKey(customers[0]);
			Assert.IsNotNull(key);
			Assert.AreEqual(key.EntityKeyValues.Length, 1);
			Assert.AreEqual(key.EntityKeyValues[0].Value, customers[0].CustomerID);
			Assert.That(() => repo.CreateKey(null), Throws.TypeOf<ArgumentNullException>());
		}

		[Test]
		public void TestTryGetAttachedObjectByKey()
		{
			var context = new DbContext1<Customer>(Constants.ConnectionString, true, true);
			var repo = new EFRepository<Customer>(context);
			var customers = repo.GetQuery().ToArray();
			var key = repo.CreateKey(customers[0]);
			Customer customer;
			var result = repo.TryGetAttachedObjectByKey(key, out customer);
			Assert.IsTrue(result);
			Assert.AreSame(customer, customers[0]);
			key = repo.CreateKey(new Customer { CustomerID = "TestCustomer" });
			result = repo.TryGetAttachedObjectByKey(key, out customer);
			Assert.IsFalse(result);
			Assert.IsNull(customer);
			Assert.That(() => repo.TryGetAttachedObjectByKey(null, out customer), Throws.TypeOf<ArgumentNullException>());
		}

	}
}
